package truerefactor.refactor;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import truerefactor.graph.CodeNode;

/**
 * RefactoringFactory - A factory class used to generate instances of
 * refactorings to be used by the GA
 * 
 * @author Isaac Griffith
 */
public class RefactoringFactory {

    /**
     * The singleton instance of this class
     */
    private static RefactoringFactory instance = null;
    /**
     * List of classes representing the available refactorings
     */
    List<Class> availableRefactorings;

    /**
     * Creates a new list refactoring factory with a populated list of available
     * refactorings
     */
    private RefactoringFactory()
    {
        availableRefactorings = new ArrayList<Class>();
        availableRefactorings.add(MoveMethod.class);
        availableRefactorings.add(MoveField.class);
        availableRefactorings.add(InlineClass.class);
        availableRefactorings.add(EncapsulateField.class);
        availableRefactorings.add(SelfEncapsulateField.class);
        availableRefactorings.add(ExtractInterface.class);
        availableRefactorings.add(PullUpField.class);
        availableRefactorings.add(PullUpMethod.class);
        availableRefactorings.add(PushDownField.class);
        availableRefactorings.add(PushDownMethod.class);
    }

    /**
     * @return the singleton instance of this class.
     */
    public static RefactoringFactory getInstance()
    {
        if (instance == null)
            instance = new RefactoringFactory();

        return instance;
    }

    /**
     * Creates a new InlineClass for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type InlineClass
     */
    public Refactoring createInlineClass(CodeNode... nodes)
    {
        return createRefactoring(InlineClass.class, nodes);
    }

    /**
     * Creates a new CollapseHierarchy for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type CollapseHierarchy
     */
    public Refactoring createCollapseHierarchy(CodeNode... nodes)
    {
        return createRefactoring(CollapseHierarchy.class, nodes);
    }

    /**
     * Creates a new AddParameter for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type AddParameter
     */
    public Refactoring createAddParameter(CodeNode... nodes)
    {
        return createRefactoring(AddParameter.class, nodes);
    }

    /**
     * Creates a new ChangeBiDirectional for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type ChangeBiDirectional
     */
    public Refactoring createChangeBiDirectional(CodeNode... nodes)
    {
        return createRefactoring(ChangeBiDirectional.class, nodes);
    }

    /**
     * Creates a new ChangeUniDirectional for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type ChangeUniDirectional
     */
    public Refactoring createChangeUniDirectional(CodeNode... nodes)
    {
        return createRefactoring(ChangeUniDirectional.class, nodes);
    }

    /**
     * Creates a new DelegationToInheritance for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type DelegationToInheritance
     */
    public Refactoring createDelegationToInheritance(CodeNode... nodes)
    {
        return createRefactoring(DelegationToInheritance.class, nodes);
    }

    /**
     * Creates a new EncapsulateField for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type EncapsulateField
     */
    public Refactoring createEncapsulateField(CodeNode... nodes)
    {
        return createRefactoring(EncapsulateField.class, nodes);
    }

    /**
     * Creates a new ExtractInterface for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type ExtractInterface
     */
    public Refactoring createExtractInterface(CodeNode... nodes)
    {
        return createRefactoring(ExtractInterface.class, nodes);
    }

    /**
     * Creates a new ExtractMethod for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type ExtractMethod
     */
    public Refactoring createExtractMethod(CodeNode... nodes)
    {
        return createRefactoring(ExtractMethod.class, nodes);
    }

    /**
     * Creates a new HideDelegate for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type HideDelegate
     */
    public Refactoring createHideDelegate(CodeNode... nodes)
    {
        return createRefactoring(HideDelegate.class, nodes);
    }

    /**
     * Creates a new InheritanceToDelegation for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type InheritanceToDelegation
     */
    public Refactoring createInheritanceToDelegation(CodeNode... nodes)
    {
        return createRefactoring(InheritanceToDelegation.class, nodes);
    }

    /**
     * Creates a new InlineMethod for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type InlineMethod
     */
    public Refactoring createInlineMethod(CodeNode... nodes)
    {
        return createRefactoring(InlineMethod.class, nodes);
    }

    /**
     * Creates a new InlineTemp for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type InlineTemp
     */
    public Refactoring createInlineTemp(CodeNode... nodes)
    {
        return createRefactoring(InlineTemp.class, nodes);
    }

    /**
     * Creates a new IntroduceParamObject for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type IntroduceParamObject
     */
    public Refactoring createIntroduceParamObject(CodeNode... nodes)
    {
        return createRefactoring(IntroduceParamObject.class, nodes);
    }

    /**
     * Creates a new MoveField for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type MoveField
     */
    public Refactoring createMoveField(CodeNode... nodes)
    {
        return createRefactoring(MoveField.class, nodes);
    }

    /**
     * Creates a new MoveMethod for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type MoveMethod
     */
    public Refactoring createMoveMethod(CodeNode... nodes)
    {
        return createRefactoring(MoveMethod.class, nodes);
    }

    /**
     * Creates a new PullUpField for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type PullUpField
     */
    public Refactoring createPullUpField(CodeNode... nodes)
    {
        return createRefactoring(PullUpField.class, nodes);
    }

    /**
     * Creates a new PullUpMethod for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type PullUpMethod
     */
    public Refactoring createPullUpMethod(CodeNode... nodes)
    {
        return createRefactoring(PullUpMethod.class, nodes);
    }

    /**
     * Creates a new PushDownField for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type PushDownField
     */
    public Refactoring createPushDownField(CodeNode... nodes)
    {
        return createRefactoring(PushDownField.class, nodes);
    }

    /**
     * Creates a new PushDownMethod for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type PushDownMethod
     */
    public Refactoring createPushDownMethod(CodeNode... nodes)
    {
        return createRefactoring(PushDownMethod.class, nodes);
    }

    /**
     * Creates a new RemoveMiddleMan for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type RemoveMiddleMan
     */
    public Refactoring createRemoveMiddleMan(CodeNode... nodes)
    {
        return createRefactoring(RemoveMiddleMan.class, nodes);
    }

    /**
     * Creates a new RemoveSettingMethod for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type RemoveSettingMethod
     */
    public Refactoring createRemoveSettingMethod(CodeNode... nodes)
    {
        return createRefactoring(RemoveSettingMethod.class, nodes);
    }

    /**
     * Creates a new SelfEncapsulateField for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type SelfEncapsulateField
     */
    public Refactoring createSelfEncapsulateField(CodeNode... nodes)
    {
        return createRefactoring(SelfEncapsulateField.class, nodes);
    }

    /**
     * Creates a new SeparateQueryFromModifier for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type SeparateQueryFromModifier
     */
    public Refactoring createSeparateQueryFromModifier(CodeNode... nodes)
    {
        return createRefactoring(SeparateQueryFromModifier.class, nodes);
    }

    /**
     * Creates a new SplitTemporaryVariable for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type SplitTemporaryVariable
     */
    public Refactoring createSplitTemporaryVariable(CodeNode... nodes)
    {
        return createRefactoring(SplitTemporaryVariable.class, nodes);
    }

    /**
     * Creates a new SubstitueAlgorithm for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type SubstitueAlgorithm
     */
    public Refactoring createSubstituteAlgorithm(CodeNode... nodes)
    {
        return createRefactoring(SubstituteAlgorithm.class, nodes);
    }

    /**
     * Creates a new ReplaceTempWithQuery for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type ReplaceTempWithQuery
     */
    public Refactoring createReplaceTempWithQuery(CodeNode... nodes)
    {
        return createRefactoring(ReplaceTempWithQuery.class, nodes);
    }

    /**
     * Creates a new ExtractClass for the given operands
     * 
     * @param nodes
     *            operands
     * @return Refactoring of type ExtractClass
     */
    public Refactoring createExtractClass(CodeNode... nodes)
    {
        return createRefactoring(ExtractClass.class, nodes);
    }

    /**
     * Creates a refactoring using Java Reflection based on the provided class
     * and list of operand CodeNodes
     * 
     * @param refactoring
     *            Class of some refactoring to be instantiated
     * @param nodes
     *            list of operand code nodes
     * @return an instance of a Refactoring
     */
    private Refactoring createRefactoring(Class refactoring, CodeNode... nodes)
    {
        List<CodeNode> operands = new LinkedList<CodeNode>();
        Refactoring retVal = null;
        for (int i = 0; i < nodes.length; i++)
            operands.add(nodes[i]);

        if (Refactoring.class.isAssignableFrom(refactoring))
        {
            try
            {
                Constructor con = refactoring.getConstructor(null);
                retVal = (Refactoring) con.newInstance();
                retVal.setOperands(operands);
            }
            catch (SecurityException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            catch (NoSuchMethodException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            catch (IllegalArgumentException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            catch (InstantiationException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            catch (IllegalAccessException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            catch (InvocationTargetException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

        return retVal;
    }
}
